﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RecommenderSystem.misc;


namespace RecommenderSystem.algorithms
{
    public class Cosine: MemoryBased
    {
        //Caching users similarity
        private Cache similarityCache;
        public readonly string ALGORITHM_NAME = "Cosine";
        public Cosine(Dictionary<string, User> users, ICollection<IDataRecord> dataset) : base(users, dataset) 
        {
            similarityCache = new Cache();
        }

        public Cosine(Dictionary<string, User> users, ICollection<IDataRecord> dataset, Dictionary<string, Cache> cache)
            : base(users, dataset)
        {
            if (!cache.ContainsKey(ALGORITHM_NAME))
                cache.Add(ALGORITHM_NAME, new Cache());

            similarityCache = cache[ALGORITHM_NAME];
        }


        public override double calculateSimilarity(User user, User other)
        {

            if (similarityCache.isCached(user.getUserId(), other.getUserId()))
            {
                return similarityCache.retrieveFromCache(user.getUserId(), other.getUserId());
            }

            var items = user.getRatedItemsIds().Union(other.getRatedItemsIds());

            List<double> v = new List<double>();
            List<double> u = new List<double>();

            foreach (string item in items)
            {
                u.Add(user.getRatedItemsIds().Contains(item) ? user.getItemRate(item) : 0);
                v.Add(other.getRatedItemsIds().Contains(item) ? other.getItemRate(item) : 0);
            }
            double usersSimilarity = cosineSimilarity(u, v);

            similarityCache.addToCache(user.getUserId(), other.getUserId(), usersSimilarity);
            similarityCache.addToCache(other.getUserId(), user.getUserId(), usersSimilarity);

            return usersSimilarity; 
        }

        private static double cosineSimilarity(List<double> V1, List<double> V2)
        {
            int N = 0;
            N = ((V2.Count < V1.Count) ? V2.Count : V1.Count);
            double dot = 0.0d;
            double mag1 = 0.0d;
            double mag2 = 0.0d;
            for (int n = 0; n < N; n++)
            {
                dot += V1[n] * V2[n];
                mag1 += Math.Pow(V1[n], 2);
                mag2 += Math.Pow(V2[n], 2);
            }

            return dot / (Math.Sqrt(mag1) * Math.Sqrt(mag2));
        }

    }
}
